TS study: νƒ€μž… μΆ”λ‘ (1)

@choi2021 Β· January 24, 2023 Β· 11 min read

πŸ™‹β€β™‚οΈ νƒ€μž…μΆ”λ‘ 

νƒ€μž…μΆ”λ‘ μ€ νƒ€μž…μŠ€ν¬λ¦½νŠΈκ°€ μš°λ¦¬κ°€ μž‘μ„±ν•œ μ½”λ“œμ— λŒ€ν•΄μ„œ μžλ™μœΌλ‘œ νƒ€μž…μ„ μΆ”λ‘ ν•΄μ£ΌλŠ” 것을 μ˜λ―Έν•œλ‹€.

const person = {
  name: "so",
  born: {
    where: "asd",
    when: "1233",
  },
  died: {
    where: "asi",
    when: "nov,201",
  },
}

// νƒ€μž…μŠ€ν¬λ¦½νŠΈλ‘œ μΆ”λ‘ λœ νƒ€μž…
const person: {
  name: string
  born: {
    where: string
    when: string
  }
  died: {
    where: string
    when: string
  }
}

μœ„ person 예제λ₯Ό λ³΄λ©΄μ„œ νƒ€μž… 좔둠이 μ •ν™•ν•˜κ²Œ μž‘λ™ν•˜κ³  μžˆλŠ” 것을 λ³Ό 수 μžˆλ‹€. νƒ€μž… 좔둠을 μ΄μš©ν–ˆμ„ λ•Œ 더 μ •ν™•ν•˜κ²Œ νƒ€μž…μ΄ μ •ν•΄μ§€κ±°λ‚˜, ꡳ이 λͺ…μ‹œμ μœΌλ‘œ νƒ€μž…μ„ μ •ν•  ν•„μš”κ°€ 없을 λ•Œμ—λŠ” νƒ€μž… ꡬ문을 μƒλž΅ν•˜λŠ” 게 가독성을 λ†’μ—¬μ€€λ‹€.

interface Product {
  id: string
  name: string
  price: number
}

// λͺ…μ‹œμ μœΌλ‘œ νƒ€μž…μ„ λ‹€ μ •ν•œ 경우
function logProduct(product: Product) {
  const id: number = product.id
  const name: string = product.name
  const price: number = product.price
  console.log(id, name, price)
}

function logProduct(product: Product) {
  const { id, name, price } = product
  console.log(id, name, price)
}

μœ„ μ˜ˆμ œμ—μ„œ Product interfaceμ—μ„œ νƒ€μž…μ„ 이미 μ •μ˜ν–ˆκΈ° λ•Œλ¬Έμ— ꡳ이 ν•¨μˆ˜ λ‚΄λΆ€μ—μ„œ μ •μ˜ν•  ν•„μš”κ°€ μ—†μ—ˆλ‹€.

그러면 항상 νƒ€μž… 좔둠에 λ§‘κΈ°λ©΄ λ˜λŠ” 걸까?

μ•žμ„œ μ •λ¦¬ν–ˆλ˜ νƒ€μž…μ‹œμŠ€ν…œμ„ 톡해 λ°°μ› λ˜ 객체 λ¦¬ν„°λŸ΄κ³Ό ν•¨μˆ˜μ˜ μΈμžμ™€ λ°˜ν™˜κ°’μ€ λͺ…μ‹œμ μœΌλ‘œ νƒ€μž…μ„ μ •μ˜ν•΄ 쀄 ν•„μš”κ°€ μžˆλ‹€.

λ¨Όμ € 객체 λ¦¬ν„°λŸ΄μ˜ 경우 λͺ…μ‹œμ μœΌλ‘œ νƒ€μž…μ„ μ •μ˜ν•˜λ©΄ μž‰μ—¬ 속성 체크가 λ™μž‘ν•΄ μž‘μ„±λœ νƒ€μž…κ³Ό 비ꡐ해 μ˜€νƒ€λ‚˜ 였λ₯˜λ₯Ό μž‘λŠ”λ° 도움을 쀄 수 μžˆλ‹€.

ν•¨μˆ˜μ˜ 경우 λ°˜ν™˜ 값을 톡해 였λ₯˜λ₯Ό 막을 수 μžˆλ‹€. λ‹€μŒ 예제λ₯Ό 보자.

const cache: { [ticker: string]: number } = {}
function getQuote(ticker: string): Promise<number> {
  if (ticker in cache) {
    return cache[ticker] // Promise<number>κ°€ μ•„λ‹ˆλΌ μ—λŸ¬
  }
  return fetch(`https://quotes.example.com/?q=${ticker}`)
    .then(response => response.json())
    .then(quote => {
      cache[ticker] = quote
      return quote
    })
}

μœ„ μ½”λ“œλŠ” 이미 μ‘΄μž¬ν•˜λŠ” κ°’μ˜ 경우 cache 데이터λ₯Ό κ°€μ Έμ˜€κ³  cache된 값이 μ—†λ‹€λ©΄ μƒˆλ‘œ μš”μ²­ν•˜λŠ” ν•¨μˆ˜λ‹€. ν•˜μ§€λ§Œ cache값이 없을 λ•Œ number둜 λ°˜ν™˜λ˜κΈ° λ•Œλ¬Έμ— μ—λŸ¬λ₯Ό λ˜μ Έμ£ΌλŠ” 것을 λ³Ό 수 μžˆλ‹€.

이처럼 λ°˜ν™˜ νƒ€μž…μ„ λͺ…μ‹œν•¨μœΌλ‘œμ¨ μ½”λ“œ 자체적으둜 인자λ₯Ό λ„£μ—ˆμ„ λ•Œ κ²°κ³Ό 값을 μ˜ˆμΈ‘ν•΄ λ¬Έμ„œλ‘œμ¨ 역할을 ν•  수 있으며, 기쑴의 λͺ…λͺ…λœ νƒ€μž…μ„ κ·ΈλŒ€λ‘œ μ΄μš©ν•  수 μžˆλŠ” μž₯점도 κ°€μ§„λ‹€. μ•„λž˜ μ½”λ“œλ₯Ό 보자.

interface Vector2D {
  x: number
  y: number
}

function add(a: Vector2D, b: Vector2D) {
  return { x: a.x + b.x, y: a.y + b.y } // {x:number y:number}
}

function add(a: Vector2D, b: Vector2D): Vector2D {
  return { x: a.x + b.x, y: a.y + b.y }
}

λ°˜ν™˜κ°’ νƒ€μž…λ„ λ™μΌν•˜κ²Œ Vector 2Dλ₯Ό μ˜ˆμƒν–ˆμ§€λ§Œ λ‹€λ₯Έ νƒ€μž…μœΌλ‘œ λ°˜ν™˜ 값이 μΆ”λ‘ λ˜λŠ” 것을 λ³Ό 수 μžˆλ‹€.

정리해보면 λͺ…μ‹œμ  νƒ€μž…μ΄ ν•„μš”ν•œ κ²½μš°λŠ” κ°μ²΄λ¦¬ν„°λŸ΄μ„ μ„ μ–Έν•΄ μž‰μ—¬μ†μ„±μ²΄ν¬κ°€ ν•„μš”ν•˜κ±°λ‚˜, ν•¨μˆ˜μ˜ μΈμžμ™€ λ°˜ν™˜κ°’μ— ν•„μš”ν•˜λ©°, λŒ€λΆ€λΆ„μ˜ 경우 νƒ€μž… 좔둠에 맑겨도 λœλ‹€κ³  ν•œλ‹€.

πŸ“¦ λ‹€λ₯Έ νƒ€μž…μ— λ‹€λ₯Έ λ³€μˆ˜ μ“°κΈ°

μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” 동적 νƒ€μž… 언어이기 λ•Œλ¬Έμ— 같은 λ³€μˆ˜μ— λ‹€λ₯Έ νƒ€μž…μ„ ν• λ‹Ήν•  수 μžˆμ§€λ§Œ νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” ν• λ‹Ήν•  수 μ—†λ‹€. 이 κ²½μš°μ— λ‹€λ₯Έ νƒ€μž…μ„ ν• λ‹Ήν•˜κΈ° μœ„ν•΄μ„œ μœ λ‹ˆμ˜¨ νƒ€μž…μœΌλ‘œ νƒ€μž…μ„ 더 쒁힐 수 μžˆλ‹€.

let id: string | number = "12-34-56"
id = 123

ν•˜μ§€λ§Œ μ΄λŸ¬ν•œ κ²½μš°μ— 였히렀 stringκ³Ό number의 곡톡 λ©”μ†Œλ“œλ§Œ μ œκ³΅ν•΄μ£ΌλŠ” λ“± μ‚¬μš©ν•˜κΈ° 더 μ–΄λ €μ›Œμ§„λ‹€. 차라리 λ³„λ„λ‘œ λ³€μˆ˜λ₯Ό λ‚˜λˆ„λŠ” 것이 λ‚˜μ€ 방법이닀.

🎈 νƒ€μž… λ„“νžˆκΈ°

νƒ€μž…μŠ€ν¬λ¦½νŠΈκ°€ νƒ€μž…μ„ μΆ”λ‘ ν•  λ•ŒλŠ” ν• λ‹Ήλœ 값을 톡해 ν• λ‹Ή κ°€λŠ₯ν•œ κ°’λ“€μ˜ 집합을 μœ μΆ”ν•œλ‹€.

interface Vector3 {
  x: number
  y: number
  z: number
}

function getComponent(vector: Vector3, axis: "x" | "y" | "z") {
  return vector[axis]
}

let x = "x"
let vec = { x: 10, y: 20, z: 30 }
getComponent(vec, x) // Argument of type 'string' is not assignable to parameter of type '"x" | "y" | "z"'

μœ„ μ˜ˆμ œμ—μ„œ let x= 'x'둜 μ„ μ–Έλ˜μ–΄ xλ³€μˆ˜μ—λŠ” ν• λ‹Ήν•  수 μžˆλŠ” νƒ€μž…μ„ κ³ λ €ν•΄ stringνƒ€μž…μœΌλ‘œ μΆ”λ‘ ν–ˆλ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— μ •ν™•νžˆ "x" | "y" | "z"κ°€ ν•„μš”ν•œ getComponentν•¨μˆ˜μ˜ 인자둜 전달 μ‹œ νƒ€μž… μ—λŸ¬κ°€ λ°œμƒν–ˆλ‹€.

μ΄λŸ¬ν•œ 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„œλŠ” κ°„λ‹¨ν•˜κ²Œ letλŒ€μ‹  const둜 μˆ˜μ •ν•  수 μžˆλ‹€. const둜 μ„ μ–Έν•˜λ©΄ x의 νƒ€μž…μ΄ 'x'κ°€ λ˜μ–΄ νƒ€μž…μ΄ 더 μ’ν˜€μ§€κΈ° λ•Œλ¬Έμ— μ•žμ„œ λ°œμƒν•œ μ—λŸ¬λ₯Ό 막을 수 μžˆλ‹€.

ν•˜μ§€λ§Œ const둜 μ„ μ–Έν•˜λŠ” 방법은 객체와 λ°°μ—΄μ—μ„œλŠ” μ—¬μ „νžˆ λ¬Έμ œκ°€ λœλ‹€. λ°°μ—΄μ˜ 경우의 const x= [1,2,3]라고 ν–ˆμ„ λ•Œ number[]둜 봐야할 μ§€ [number,number,number]둜 ν•΄μ•Όν•  μ§€ νƒ€μž…μΆ”λ‘ λ§ŒμœΌλ‘œλŠ” μ–΄λ ΅λ‹€.

μ΄λŸ¬ν•œ λ¬Έμ œμ μ„ ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„œλŠ” λ¨Όμ € λͺ…μ‹œμ  νƒ€μž…μ„ 전달해 μ£ΌλŠ” 방법이 μžˆλ‹€. μ •ν™•νžˆ λ‚΄κ°€ μ›ν•˜λŠ” νƒ€μž…μœΌλ‘œ 정해쀄 수 μžˆλ‹€.

const arr = [1, 2, 3] // number[]
const arr2: [number, number, number] = [1, 2, 3]

두 λ²ˆμ§Έλ‘œλŠ” as const νƒ€μž… 단언을 톡해 μ΅œλŒ€ν•œ 쒁은 νƒ€μž…μœΌλ‘œ μΆ”λ‘ ν•˜λŠ” 방법이 μžˆλ‹€.

const arr = [1, 2, 3] as const // readonly [1,2,3]

πŸ™ νƒ€μž… 쒁히기

null 체크/ undefined 체크

νƒ€μž… 쒁히기λ₯Ό λ‚΄κ°€ κ°€μž₯ 많이 썼던 κ²½μš°λŠ” null 체크 λ˜λŠ” undefined μ²΄ν¬μ˜€λ˜ 것 κ°™λ‹€. μ±…μ—μ„œλ„ λŒ€ν‘œμ μΈ μ˜ˆμ‹œλ‘œ null 체크λ₯Ό 보여쀀닀.

const el = document.getElementById("foo") // HTMLElement | null
if (!el) throw new Error("Unable to find #foo")
el.innerHTML = "Party Time".blink()

μœ„ μ˜ˆμ œμ—μ„œ el값이 null이 될 수 있기 λ•Œλ¬Έμ— 쑰건문으둜 λΆ„κΈ° 처리λ₯Ό ν•΄μ€€ 것을 λ³Ό 수 μžˆλ‹€.

instanceof와 λ‚΄μž₯ ν•¨μˆ˜

instanceof와 λ‚΄μž₯ ν•¨μˆ˜λ₯Ό μ΄μš©ν•΄ νƒ€μž…μ„ 쒁힐 수 μžˆλ‹€. instanceofλ₯Ό 이용 μ‹œμ— μ€‘μš”ν–ˆλ˜ 점은 λŸ°νƒ€μž„ μ—°μ‚°μžλ‘œ prototype chain에 ν•΄λ‹Ή νƒ€κ²Ÿμ΄ μžˆλŠ”μ§€ ν™•μΈν•˜κ³  값을 ν™•μΈν•˜λŠ” 것을 μ΄ν•΄ν•˜κ³  μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.

function contains(text: string, search: string | RegExp) {
  if (search instanceof RegExp) {
    search
    return !!search.exec(text)
  }
  return text.includes(search)
}

function contains(text: string, terms: string | string[]) {
  const termList = Array.isArray(terms) ? terms : [terms]
  termList
}

속성체크와 discriminated Union Type

또 λ‹€λ₯Έ νƒ€μž…μ„ μ’νžˆλŠ” λ°©λ²•μœΌλ‘œ 객체 νƒ€μž…μ˜ 경우 속성 체크λ₯Ό μ΄μš©ν•΄ 쒁힐 수 있고, κ³΅ν†΅μ˜ μ†μ„±μ˜ λ‹€λ₯Έ 값을 κ°€μ§€κ²Œ ν•˜λŠ” discriminated union κ΅¬λ³„λœ μœ λ‹ˆμ˜¨ νƒ€μž…μ„ μ΄μš©ν•  수 μžˆλ‹€. μ‹€μ œ μ‚¬μš© κ²½ν—˜μ€ 속성 체크와 discriminated Union Type은 union Type으둜 μ „λ‹¬ν•œ propsλ₯Ό λΆ„κΈ° μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ 많이 μ‚¬μš©ν–ˆλ‹€.

interface A {
  a: number
}
interface B {
  b: number
}

function pickAB(ab: A | B) {
  if ("a" in ab) {
    ab
  } else {
    ab
  }
  ab
}

interface UploadEvent {
  type: "upload"
  filename: string
  contents: string
}
interface DownloadEvent {
  type: "download"
  filename: string
}

type AppEvent = UploadEvent | DownloadEvent

function handleEvent(e: AppEvent) {
  switch (e.type) {
    case "download":
      e // DownloadEvent
      break
    case "upload":
      e // UploadEvent
      break
  }
}

μ‚¬μš©μž μ •μ˜ νƒ€μž… κ°€λ“œ

νƒ€μž…μ„ 쒁히기 μœ„ν•œ λ§ˆμ§€λ§‰ λ°©λ²•μœΌλ‘œ ν•¨μˆ˜λ₯Ό μ΄μš©ν•΄ λ‚΄κ°€ μ›ν•˜λŠ” νƒ€μž…μœΌλ‘œ μ’ν˜€μ€„ 수 μžˆλ‹€.

function isInputElement(el: HTMLElement): el is HTMLInputElement {
  return "value" in el
}

function getElementContent(el: HTMLElement) {
  if (isInputElement(el)) {
    el // HTMLInputElement
    return el.value
  }
  el // HTMLElement
  return el.textContent
}

ν•œκΊΌλ²ˆμ— 객체 μƒμ„±ν•˜κΈ°

객체λ₯Ό 생성할 λ•Œ μ£Όμ˜ν•  점은 λ™μ μœΌλ‘œ 속성을 μΆ”κ°€ν•˜κΈ° 보닀 ν•„μš”ν•œ 속성듀을 ν•œλ²ˆμ— 생성해야 νƒ€μž…μΆ”λ‘ μ˜ 이점을 μ΄μš©ν•  수 μžˆλ‹€.

const pt = {}
pt.x = 3 // Property 'x' does not exist on type '{}'.
pt.y = 4 // Property 'y' does not exist on type '{}'.

μœ„ μ˜ˆμ œμ—μ„œλŠ” 처음 ptκ°€ {}둜 μΆ”λ‘ λ˜κΈ° λ•Œλ¬Έμ— μ—λŸ¬κ°€ λ°œμƒν–ˆλ‹€. μ΄λŸ¬ν•œ 원칙은 κΈ°μ‘΄ 객체 λ‚΄μš©μ„ μ΄μš©ν•΄ μƒˆλ‘œμš΄ 객체λ₯Ό λ§Œλ“€ λ•Œμ—λ„ λ™μΌν•˜κ²Œ μ μš©λœλ‹€.

const pt = { x: 3, y: 4 }
const id = { name: "Pythagoras" }
const namedPoint = {}
Object.assign(namedPoint, pt, id)
namedPoint.name // Property 'name' does not exist on type '{}'.

μœ„ μ˜ˆμ œλŠ” namedPointλŠ” λ™μΌν•˜κ²Œ {}λ₯Ό κΈ°μ€€μœΌλ‘œ 좔둠이 λ˜μ–΄ Object.assign()λ©”μ†Œλ“œλ₯Ό μ΄μš©ν•΄μ„œ 속성을 좔가해도 μ—λŸ¬κ°€ λ°œμƒλ˜μ—ˆλ‹€. 이점을 ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„œλŠ” spread operatorλ₯Ό μ΄μš©ν•  수 μžˆλ‹€.

const namedPoint = { ...pt, ...id } // const namedPoint: {name: string; x: number; y: number;}
namedPoint.name

μ •λ¦¬ν•˜λ©°

νƒ€μž… 좔둠은 μ—„μ²­λ‚˜κ²Œ νŽΈν•œ λΆ€λΆ„μ΄μ§€λ§Œ, 더 μ •ν™•ν•˜κ³  λ‚΄κ°€ μ›ν•˜λŠ” νƒ€μž…μœΌλ‘œ μ‚¬μš©ν•˜κΈ° μœ„ν•΄μ„œλŠ” λͺ…μ‹œμ μœΌλ‘œ μ‚¬μš©ν•˜κ±°λ‚˜ as constλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€λŠ” 점을 μ•Œ 수 μžˆμ—ˆλ‹€.

@choi2021
맀일의 μ‹œν–‰μ°©μ˜€λ₯Ό κΈ°λ‘ν•˜λŠ” κ°œλ°œμΌμ§€μž…λ‹ˆλ‹€.